/* -*- c++ -*-
   convert_types.cc - LLVM-IR to GCC conversion - types.
   Copyright (C) 2011 Matthew Wahab <mwb.cde@gmail.com>

   This file is part of GCC-LLVMIR.

   GCC-LLVMIR is free software; you can redistribute it and/or modify it under
   the terms of the GNU General Public License as published by the Free
   Software Foundation; either version 3, or (at your option) any later
   version.

   GCC-LLVMIR is distributed in the hope that it will be useful, but WITHOUT ANY
   WARRANTY; without even the implied warranty of MERCHANTABILITY or
   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   for more details.

   You should have received a copy of the GNU General Public License
   along with GCC-LLVMIR; see the file COPYING3.  If not see
   <http://www.gnu.org/licenses/>.  */

/* Conversion code for LLVM IR types and related items. */

#include "llvmir.h"
#include "parser.h"

#include "llvm/Type.h"
#include "llvm/DerivedTypes.h"
#include "llvm/GlobalValue.h"
#include "llvm/InlineAsm.h"
#include "llvm/Function.h"
#include "llvm/BasicBlock.h"
#include "llvm/IntrinsicInst.h"
#include "llvm/Instruction.h"
#include "llvm/Instructions.h"
#include "llvm/Constants.h"
#include "llvm/ADT/APInt.h"
#include "llvm/ADT/APFloat.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/Support/DebugLoc.h"
#include "llvm/Support/raw_os_ostream.h"

namespace llvmir
{
  /*
   * Types
   */

  /* Convert an integer type.*/
  TypeRepr* 
  Parser::convert_integer_type(Context *context,
                               const llvm::Type* llvm_type)
  {
    gcc_assert(llvm_type);
    const unsigned bitwidth = llvm_type->getPrimitiveSizeInBits();
    TypeRepr* type_repr = 0;
    tree type_tree = NULL_TREE;

    if (bitwidth == 1 
        || bitwidth == TYPE_PRECISION(boolean_type_node))
      type_tree = boolean_type_node;
    else if (bitwidth == TYPE_PRECISION(char_type_node))
      type_tree = char_type_node;
    else if (bitwidth == TYPE_PRECISION(short_integer_type_node))
      type_tree = short_integer_type_node;
    else if (bitwidth == TYPE_PRECISION(integer_type_node))
      type_tree = integer_type_node;
    else if (bitwidth == TYPE_PRECISION(long_integer_type_node))
      type_tree = long_integer_type_node;
    else if (bitwidth == TYPE_PRECISION(long_long_integer_type_node))
      type_tree = long_long_integer_type_node;
    else if (bitwidth == TYPE_PRECISION(int128_integer_type_node))
      type_tree = int128_integer_type_node;
    else
      {
        // Make a new type and precision
        type_tree = make_node(INTEGER_TYPE);
        TYPE_PRECISION(type_tree) = bitwidth;
        layout_type(type_tree);
      }
    gcc_assert(type_tree);

    type_repr = new TypeRepr(llvm_type, type_tree);
    context->register_type_repr(type_repr);
    return type_repr;
  }

  /* Convert an floating-point type.*/
  TypeRepr* 
  Parser::convert_float_type(Context *context,
                             const llvm::Type* llvm_type)
  {
    gcc_assert(llvm_type);
    const unsigned bitwidth = llvm_type->getPrimitiveSizeInBits();
    TypeRepr* type_repr = 0;
    tree type_tree = NULL_TREE;

    if (llvm_type->isFloatTy())
      type_tree = float_type_node;
    else if (llvm_type->isDoubleTy())
      {
          if (bitwidth > TYPE_PRECISION(double_type_node))
            type_tree = long_double_type_node;
          else
            type_tree = double_type_node;
      }
    else if (llvm_type->isX86_FP80Ty()) // 80-bit X86 floating-point
      type_tree = long_double_type_node;
    else if (llvm_type->isFP128Ty()) // 128-bit floating-point
      type_tree = long_double_type_node;
    else if (llvm_type->isPPC_FP128Ty()) // 128-bit floating-point
      type_tree = long_double_type_node;
    else
      error("unknown llvm floating point type.");

    gcc_assert(type_tree);

    type_repr = new TypeRepr(llvm_type, type_tree);
    context->register_type_repr(type_repr);
    return type_repr;
  }

  /* Convert an opaque type. By default, this is translated to
     (void*). */
  TypeRepr* 
  Parser::convert_opaque_type(Context *context,
                              const llvm::Type* llvm_type)
  {
    TypeRepr* type_repr = new TypeRepr(llvm_type, ptr_type_node);

    return context->register_type_repr(type_repr);
  }

  /* Convert a struct type. */
  TypeRepr*
  Parser::convert_struct_type(Context *context,
                              const llvm::Type* llvm_type)
  {
    const llvm::StructType *llvm_struct_type = 
      static_cast<const llvm::StructType*>(llvm_type);
    gcc_assert(llvm_struct_type);

    // Handle opaque types.
    if (llvm_struct_type->isOpaque())
      return convert_opaque_type(context, llvm_type);

    tree struct_type = NULL_TREE;
    tree subtype_list = NULL_TREE;
    StructTypeRepr* struct_type_repr = 0;
    const size_t num_fields = llvm_struct_type->getNumElements();

    struct_type_repr = new StructTypeRepr(llvm_struct_type, 0);
    context->register_type_repr(struct_type_repr);

    // Make the new type
    struct_type = make_node(RECORD_TYPE);
    struct_type_repr->set_tree(struct_type);

    // Iterate through the components, adding the elements into the
    // representation. Uses an index rather than an iterator to allow
    // checking the well-formedness of the resulting StructTypeRepr.
    struct_type_repr->fields.reserve(num_fields);
    for(llvm::StructType::element_iterator 
          itr = llvm_struct_type->element_begin(),
          itr_end = llvm_struct_type->element_end();
        itr != itr_end;
        ++itr)
      {
        llvm::Type* llvm_subtype = *itr;
        tree subtype_tree = NULL_TREE;
        tree field_tree = NULL_TREE;
        tree field_name_tree = NULL_TREE;
        FieldRepr* field_repr = 0;
        TypeRepr* subtype_repr = 0;

        if (!llvm_subtype)
          {
            struct_type_repr->fields.push_back(0);
            continue;
          }

        subtype_repr = convert_type(context, llvm_subtype);
        subtype_tree = copy_node(subtype_repr->get_tree());
        
        field_tree = build_decl(UNKNOWN_LOCATION,
                                FIELD_DECL,
                                field_name_tree,
                                subtype_tree);
        DECL_CONTEXT(field_tree) = struct_type;

        field_repr = new FieldRepr(subtype_repr, field_tree);
        context->register_type_repr(field_repr);

        subtype_list = chainon(subtype_list, field_tree);
        struct_type_repr->fields.push_back(field_repr);
      }
    TYPE_FIELDS(struct_type) = subtype_list;
    layout_type(struct_type);

    return struct_type_repr;
  }

  /* Convert an array type. */
  TypeRepr*
  Parser::convert_array_type(Context *context,
                             const llvm::Type* llvm_type)
  {
    const llvm::ArrayType *llvm_array_type = 
      static_cast<const llvm::ArrayType*>(llvm_type);

    gcc_assert(llvm_array_type);
    tree array_type = NULL_TREE;
    ArrayTypeRepr* array_type_repr = 0;
    tree elem_type = NULL_TREE;
    TypeRepr* elem_type_repr = 0;
    size_t llvm_num_elems = llvm_array_type->getNumElements();
    const llvm::Type* llvm_elem_type = llvm_array_type->getElementType();

    elem_type_repr = convert_type(context, llvm_elem_type);
    gcc_assert(elem_type_repr);

    elem_type = elem_type_repr->get_tree();
    gcc_assert(elem_type);

    // Make the new type
    array_type = Tree::make_array_type_cst(elem_type, llvm_num_elems);

    array_type_repr = new ArrayTypeRepr(llvm_array_type,
                                        array_type,
                                        llvm_num_elems,
                                        elem_type_repr);
    context->register_type_repr(array_type_repr);

    return array_type_repr;
  }

  /* Convert a pointer type.*/
  TypeRepr* 
  Parser::convert_pointer_type(Context *context,
                              const llvm::Type* llvm_type)
  {
    const llvm::PointerType* llvm_ptr_type = 
      static_cast<const llvm::PointerType*>(llvm_type);
    const llvm::Type* llvm_base_type = llvm_ptr_type->getElementType();
    PtrTypeRepr* ptr_type_repr = 0;
    TypeRepr* base_repr = 0;
    tree type_tree = NULL_TREE;
    tree base_type = NULL_TREE;

    base_repr = convert_type(context, llvm_base_type);
    gcc_assert(base_repr && base_repr->get_tree());

    base_type = base_repr->get_tree();
    gcc_assert(base_type);
    type_tree = build_pointer_type(base_type);

    ptr_type_repr = new PtrTypeRepr(llvm_ptr_type, type_tree);
    context->register_type_repr(ptr_type_repr);

    ptr_type_repr->base_type = base_repr;

    return ptr_type_repr;
  }

  /* Convert a vector type. GCC support for vectors (of type
     VECTOR_TYPE) is not strong so LLVM vectors are converted to single
     dimensioned GCC arrays.
  */
  TypeRepr* 
  Parser::convert_vector_type(Context *context,
                              const llvm::Type* llvm_type)
  {
    gcc_assert(llvm_type);
    const llvm::VectorType *llvm_vector_type =
      static_cast<const llvm::VectorType*>(llvm_type);
    tree vector_type = NULL_TREE;
    VectorTypeRepr* vector_type_repr = 0;
    tree elem_type = NULL_TREE;
    TypeRepr* elem_type_repr = 0;
    size_t llvm_num_elems = llvm_vector_type->getNumElements();
    const llvm::Type* llvm_elem_type = llvm_vector_type->getElementType();

    elem_type_repr = convert_type(context, llvm_elem_type);
    gcc_assert(elem_type_repr);

    elem_type = elem_type_repr->get_tree();
    gcc_assert(elem_type);

    // Make the new type
    vector_type = Tree::make_vector_type_cst(elem_type, llvm_num_elems);

    vector_type_repr = new VectorTypeRepr(llvm_vector_type,
                                          vector_type,
                                          llvm_num_elems,
                                          elem_type_repr);
    context->register_type_repr(vector_type_repr);

    return vector_type_repr;
  }

  /* Convert the void type. */
  TypeRepr* 
  Parser::convert_void_type(Context *context,
                            const llvm::Type* llvm_type)
  {
    TypeRepr* type_repr = new TypeRepr(llvm_type, void_type_node);
    return context->register_type_repr(type_repr);
  }

  /* Convert a metadata type. By default, this is translated to
     (void*). */
  TypeRepr* 
  Parser::convert_metadata_type(Context *context,
                                const llvm::Type* llvm_type)
  {
    TypeRepr* type_repr = new TypeRepr(llvm_type, ptr_type_node);

    return context->register_type_repr(type_repr);
  }

  /* Convert an LLVM type.  This is the top-level function for
     converting types. It adds newly created representation objects to
     the tables, including those created by other type conversion
     functions.
  */
  TypeRepr* 
  Parser::convert_type(Context *context,
                       const llvm::Type *llvm_type)
  {
    gcc_assert(llvm_type);
    TypeRepr* type_repr = 0;

    // Check for an existing conversion.
    type_repr = context->lookup_type(llvm_type);
    if (type_repr)
      return type_repr;

    // Integer
    if (llvm_type->isIntegerTy())
      type_repr = convert_integer_type(context, llvm_type);
    // Floating-Point
    else if (llvm_type->isFloatingPointTy())
      type_repr = convert_float_type(context, llvm_type);
    // Void
    else if (llvm_type->isVoidTy())
      type_repr = convert_void_type(context, llvm_type);
    // Function type
    else if (llvm_type->isFunctionTy())
      type_repr = convert_function_type(context, llvm_type);
    // Struct
    else if (llvm_type->isStructTy())
      type_repr = convert_struct_type(context, llvm_type);
    // Arrays
    else if (llvm_type->isArrayTy())
      type_repr = convert_array_type(context, llvm_type);
    // Pointers
    else if (llvm_type->isPointerTy())
      type_repr = convert_pointer_type(context, llvm_type);
    // Vectors
    else if (llvm_type->isVectorTy())
      type_repr = convert_vector_type(context, llvm_type);
    // Meta-data
    else if (llvm_type->isMetadataTy())
      type_repr = convert_metadata_type(context, llvm_type);
    // Unknown types.
    else 
      error ("unknown type.");

    // Should have got a type.
    gcc_assert(type_repr);
    // Double-check that the type is in the table.
    gcc_assert(context->lookup_type(llvm_type));

    return type_repr;
  }
} // namespace llvmir
